 /*******************************************************************************
  * Copyright (c) 2004, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM - Initial API and implementation
  *******************************************************************************/
 package org.eclipse.core.internal.utils;

 import java.util.*;
 import org.eclipse.core.runtime.*;
 import org.eclipse.core.runtime.jobs.*;
 import org.osgi.framework.Bundle;

 /**
  * Performs string sharing passes on all string pool participants registered
  * with the platform.
  */
 public class StringPoolJob extends Job {
     private static final long INITIAL_DELAY = 10000;//ten seconds
 private static final long RESCHEDULE_DELAY = 300000;//five minutes
 private long lastDuration;
     /**
      * Stores all registered string pool participants, along with the scheduling
      * rule required when running it.
      */
     private Map participants = Collections.synchronizedMap(new HashMap(10));

     private final Bundle systemBundle = Platform.getBundle("org.eclipse.osgi"); //$NON-NLS-1$

     public StringPoolJob() {
         super(Messages.utils_stringJobName);
         setSystem(true);
         setPriority(DECORATE);
     }

     /**
      * Adds a string pool participant. The job periodically builds
      * a string pool and asks all registered participants to share their strings in
      * the pool. Once all participants have added their strings to the pool, the
      * pool is discarded to avoid additional memory overhead.
      *
      * Adding a participant that is equal to a participant already registered will
      * replace the scheduling rule associated with the participant, but will otherwise
      * be ignored.
      *
      * @param participant The participant to add
      * @param rule The scheduling rule that must be owned at the time the
      * participant is called. This allows a participant to protect their data structures
      * against access at unsafe times.
      *
      * @see #removeStringPoolParticipant(IStringPoolParticipant)
      * @since 3.1
      */
     public void addStringPoolParticipant(IStringPoolParticipant participant, ISchedulingRule rule) {
         participants.put(participant, rule);
         if (getState() == Job.SLEEPING)
             wakeUp(INITIAL_DELAY);
         else
             schedule(INITIAL_DELAY);
     }

     /**
      * Removes the indicated log listener from the set of registered string
      * pool participants. If no such participant is registered, no action is taken.
      *
      * @param participant the participant to deregister
      * @see #addStringPoolParticipant(IStringPoolParticipant, ISchedulingRule)
      * @since 3.1
      */
     public void removeStringPoolParticipant(IStringPoolParticipant participant) {
         participants.remove(participant);
     }

     /* (non-Javadoc)
      * Method declared on Job
      */
     protected IStatus run(IProgressMonitor monitor) {
         //if the system is shutting down, don't build
 if (systemBundle.getState() == Bundle.STOPPING)
             return Status.OK_STATUS;

         //copy current participants to handle concurrent additions and removals to map
 Map.Entry[] entries = (Map.Entry[]) participants.entrySet().toArray(new Map.Entry[0]);
         ISchedulingRule[] rules = new ISchedulingRule[entries.length];
         IStringPoolParticipant[] toRun = new IStringPoolParticipant[entries.length];
         for (int i = 0; i < toRun.length; i++) {
             toRun[i] = (IStringPoolParticipant) entries[i].getKey();
             rules[i] = (ISchedulingRule) entries[i].getValue();
         }
         final ISchedulingRule rule = MultiRule.combine(rules);
         long start = -1;
         int savings = 0;
         final IJobManager jobManager = Job.getJobManager();
         try {
             jobManager.beginRule(rule, monitor);
             start = System.currentTimeMillis();
             savings = shareStrings(toRun, monitor);
         } finally {
             jobManager.endRule(rule);
         }
         if (start > 0) {
             lastDuration = System.currentTimeMillis() - start;
             if (Policy.DEBUG_STRINGS)
                 Policy.debug("String sharing saved " + savings + " bytes in: " + lastDuration); //$NON-NLS-1$ //$NON-NLS-2$
 }
         //throttle frequency if it takes too long
 long scheduleDelay = Math.max(RESCHEDULE_DELAY, lastDuration*100);
         if (Policy.DEBUG_STRINGS)
             Policy.debug("Rescheduling string sharing job in: " + scheduleDelay); //$NON-NLS-1$
 schedule(scheduleDelay);
         return Status.OK_STATUS;
     }

     private int shareStrings(IStringPoolParticipant[] toRun, IProgressMonitor monitor) {
         final StringPool pool = new StringPool();
         for (int i = 0; i < toRun.length; i++) {
             if (monitor.isCanceled())
                 break;
             final IStringPoolParticipant current = toRun[i];
             SafeRunner.run(new ISafeRunnable() {
                 public void handleException(Throwable exception) {
                     //exceptions are already logged, so nothing to do
 }

                 public void run() {
                     current.shareStrings(pool);
                 }
             });
         }
         return pool.getSavedStringCount();
     }
 }

